#pragma once

#include <vector>

#include <engine.core/Device.hpp>
#include <engine.core/vertices.hpp>
#include <engine.core/Info.hpp>

#include "exports.hpp"

#include "Mesh.hpp"
#include "Texture.hpp"
#include "Effect.hpp"
#include "Surface.hpp"
#include "Camera.hpp"

#include "RenderNode.hpp"

// PostProcessing Filtergraph
/*#include "IFilterGraph.h"
#include "ColorSource.h"
#include "DepthSource.h"
#include "GlowSource.h"
#include "ImageFilter.h"
#include "FogFilter.h"
#include "DepthFilter.h"
#include "BlendFilter.h"
#include "ScreenView.h"*/

#pragma warning (push)
#pragma warning (disable: 4251)




namespace Engine
{
	namespace Video
	{
		class ENGINE_VIDEO_API Device : public ::Engine::Device
		{
		public:

			typedef boost::shared_ptr<Device>    ptr;
			friend class Engine::EngineImpl;

		public:

			Device();
			virtual ~Device();

			/**
			 * Reports the Device's name.
			 */
			QString name() const;

			/**
			 * Change the window mode.
			 * The VideoDevice attempts to change the window mode, according to the parameters.
			 * This may or may not work. In case it didn't work, the previous mode will be restored
			 * (when possible).
			 *
			 * @param width    the new width of the window
			 * @param height   the new height of the window
			 *
			 * @return true    when changing the window mode worked
			 * @return false   when changing  the window mode didn't work, but the previous mode could be restored
			 *
			 * @note   this function directly changes the HWND's position & state
			 *
			 * @throw   boostext::exception in case the previous mode could not be restored. It is advised that the application quits in that case
			 */
			virtual bool setDisplayMode(const DisplayMode& displayMode) = 0;
			virtual DisplayMode displayMode() const = 0;

			virtual intsize2 size() const = 0;
			virtual uint32 width() const = 0;
			virtual uint32 height() const = 0;
			virtual float  aspect() const = 0;

			/**
			 * Get a list of all video adapters on this system.
			 * Updates the list of adapters first (in case an adapter has been
			 * added or removed).
			 */
			virtual VideoAdapters adapters() = 0;

			/**
			 * Get the video adapter, that is currently used by this device.
			 * Updates the list of adapters first (in case an adapter has been
			 * added or removed).
			 */
			virtual VideoAdapter currentAdapter() = 0;

			/**
			 * Query all adapters beforehand, to chose a particular one when
			 * initializing the video device.
			 */
			static VideoAdapters queryAdapters(VideoType videoSystem = D3D11);

			/**
			 * Create a mesh from a list of vertices and indices.
			 * Both the list of vertices may be empty when this is not supposed to be
			 * a static mesh.
			 *
			 * @param vertices        a vector of the vertices this mesh should hold
			 * @param primitiveType   enum identifying the type of primitive this mesh holds, defaults to UnIndexTriangle
			 * @param meshType        enum identifying the type of mesh to create, defaults to Mesh::Static
			 *
			 * @throws   boostext::exception when the mesh could not be created
			 */
			template <typename Vertex>
			Mesh::ptr createMesh(const std::vector<Vertex>& vertices, Primitive::Type primitiveType = Primitive::UnindexedTriangles, Mesh::Type meshType = Mesh::Static)
			{
				// Verify that the user didn't mess it up
				Mesh::verifyMeshLayout(vertices, Indices16(), primitiveType);

				// Fetch the VertexLayout for this vertex-type
				// And get a pointer to both buffers (when possible)
				StructSignature layout     = StructSignature(Vertex());
				Format::Type   indexType  = Mesh::indexType<Index>();
				uint32         vertexSize = vertices.size() * sizeof(Vertex);
				const void*    vertexData = vertexSize ? &vertices[0] : 0;

				// Now create a mesh from that raw data
				return createMesh(vertexData, vertexSize, sizeof(Vertex), 0, 0, 0, layout, indexType, primitiveType, meshType);
			}

			/**
			 * Create a mesh from a list of vertices and indices.
			 * Both the list of vertices and indices may be empty when this is not supposed to be
			 * a static mesh.
			 *
			 * @param vertices        a vector of the vertices this mesh should hold
			 * @param indices         a vector of indices, describing the primitives
			 * @param primitiveType   enum identifying the type of primitive this mesh holds, defaults to IndexTriangle
			 * @param bufferType      enum identifying the type of buffer to create, defaults to Buffer::Static
			 *
			 * @throws   boostext::exception when the mesh could not be created
			 */
			template <typename Vertex, typename Index>
			Mesh::ptr createMesh(const std::vector<Vertex>& vertices, const std::vector<Index>& indices, Primitive::Type primitiveType = Primitive::IndexedTriangles, Buffer::Type bufferType = Buffer::Static)
			{
				// Fetch the VertexLayout for this vertex-type
				// And get a pointer to both buffers (when possible)
				StaticReflection vertexReflection = Vertex::reflect();
				Format::Type     indexType        = Mesh::indexType<Index>();

				// Now create a mesh from that raw data
				Mesh::ptr mesh = createMesh(sizeof(Vertex), sizeof(Index), vertexReflection, indexType, primitiveType, bufferType);
				mesh->update(vertices, indices);
				return mesh;
			}


			/**
			 * Create a texture from a file.
			 *
			 * @throws   boostext::exception when the texture could not be created
			 */
			Texture::ptr createTexture(const QString& fname);

			/**
			 * Create a texture from an array of data.
			 *
			 * @throws   boostext::exception when the texture could not be created
			 */
			virtual Texture::ptr createTexture(const QByteArray& data) = 0;

			/**
			 * Create an effect.
			 * As this implementation does not support the effect framework & syntax,
			 * shaders must be individually added to the effect, grouped by techniques
			 * and passes.
			 */
			virtual Effect::ptr createEffect(const QString& name) = 0;

			/**
			 * Creates a ConstantsBuffer that can be given to any shader to set variables with one
			 * API call, instead of many.
			 *
			 * @param buffer [opt]   the data to fill the buffer in the beginning
			 * @param bufferType         enum identifier that identifies the type of buffer (static, semi or dynamic), defaults to Dynamic
			 */
			template <typename Structure>
			ConstantsBuffer::ptr createConstantsBuffer(const Structure& structure = Structure(), Buffer::Type bufferType = Buffer::Dynamic)
			{
				// TODO: Use some compile time checks to verify that this is indeed a simple structure
				//       (without vtable, inheritance, etc...)

				// Fetch the buffer layout for this structure
				ConstantsBuffer::ptr buffer = createConstantsBuffer(sizeof(Structure), Structure::reflect(), bufferType);
				buffer->update(structure);
				return buffer;
			}

			/**
			 * Create a sampler state.
			 */
			virtual SamplerState::ptr createSamplerState() = 0;

			/**
			 * Create a rasterizer state.
			 */
			virtual RasterizerState::ptr createRasterizerState() = 0;

			/**
			 * Create blend state.
			 */
			virtual BlendState::ptr createBlendState() = 0;



			/* The Renderer is a system that is much more easier to
			   use than the render-pipeline of D3D or OpenGL.
			   It is another abstraction over the video device and
			   uses the own shader and mesh abstraction to render complex
			   scenes and effects.
			   */
			virtual void render(const RenderNode::ptr& node) = 0;
			//virtual FilterGraphPtr filterGraph() const = 0;

			/**
			 * Get the Camera to change it's position, POV, etc...
			 */
			virtual Camera* camera() = 0;



			/**
			 * Creates a video device.
			 */
			static ptr create(const EngineInfo& info);

		private:

			/**
			 * Resize the entire video device's window.
			 */
			virtual void resize(uint32 width, uint32 height) = 0;

			/**
			 * Change the output window.
			 */
			virtual void changeWindow(HWND newWindow) = 0;

			/**
			 * Create a mesh from raw data.
			 * Don't use this function to create your mesh, instead use the overloaded template function.
			 *
			 * @param sizePerVertex      the size of one vertex in bytes
			 * @param sizePerIndex       the size of one index in bytes
			 * @param vertexReflection   the reflection data that represents the vertex layout
			 * @param indexType          enum identifier that describes the type of the index
			 * @param primitiveType      enum identifier that describes the primitives of the mesh
			 * @param meshType           enum identifier that describes the mesh type (static, semi- or dynamic)
			 *
			 * @throws   boostext::exception when the mesh could not be created
			 */
			virtual Mesh::ptr createMesh(uint32 sizePerVertex, uint32 sizePerIndex, const StaticReflection& vertexReflection, Format::Type indexType, Primitive::Type primitiveType, Mesh::Type meshType) = 0;

			/**
			 * Create a ConstantsBuffer.
			 *
			 * @param size               the total size of the buffer
			 * @param bufferReflection   the reflection data that represents the buffer layout
			 * @param bufferType         enum identifier that identifies the type of buffer (static, semi or dynamic)
			 */
			virtual ConstantsBuffer::ptr createConstantsBuffer(uint32 size, const StaticReflection& bufferReflection, Buffer::Type bufferType) = 0;
		};
		///////////////////////////////////////////////////////////////////////////////////////////
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma warning (pop)
